#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<assert.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>

#define FINISH 0
#define ERROR_CHILD_RESULT 97
#define ERROR_CHILD 98
#define ERROR 99
#define DELIM " "
#define ARGC_SIZE 32
#define LEFT "["
#define RIGHT "]"
#define LINE_SIZE 1024
#define IN_RDIR 1
#define OUT_RDIR 2
#define APPEND_RDIR 3
char commandline[LINE_SIZE];
char* argv[ARGC_SIZE];//命令行参数的保存，如ls -a -l --> "ls" "-a" "-l"
char pwd[32];
char * filename = NULL;
int RDIR = 0;
char* getpwd()
{
    return getcwd(pwd,sizeof(pwd));
}

const char *getusername()
{
    return getenv("USER");
}

const char *Gethostname()
{
    return getenv("HOSTNAME");
}

int check_redir(char *cline)
{
    char* pos = cline;
    while(*pos)
    {
        if(*pos == '>')
        {
            *pos++ = '\0';
            if(*pos == '>')
            {
              *pos++ = '\0';
              while(isspace(*pos)) pos++;
              filename = pos;
              RDIR = APPEND_RDIR;
              return 1;
            }
            while(isspace(*pos)) pos++;
            filename = pos;
            RDIR = OUT_RDIR;
            return 1;
        }
        else if(*pos == '<')
        {
           *pos++ = '\0';
            while(isspace(*pos)) pos++;
            filename = pos;
            RDIR = IN_RDIR;
            return 1;
        }
        else pos++;
    }
}
int Interact(char* cline, int size)
{
    getpwd();
    printf(LEFT"TEST::%s@%s %s"RIGHT,getusername(),Gethostname(),pwd);
    if(!strcmp(getusername(),"root") == 0)
        printf("$");
    else printf("#");

    char* s = fgets(cline,size,stdin);
    (void)s;//编译器可能会对创建后不适用的变量报错
    //printf("s = %s;cline=%s;",s,cline);
    cline[strlen(cline)-1] = '\0';//将输入的回车给舍弃掉
    //printf("after s:%s",cline);
    check_redir(cline);
}

int Splitstring(char* commandline, char* _argv[])//将命令行参数传到指针数组argv里
{
        if(!strlen(commandline)) return 0;
        int i = 0;
        _argv[i++] = strtok(commandline,DELIM);
        while(_argv[i++] = strtok(NULL,DELIM)){}
        return i - 1;
}

int CommandExceut(char* _argv[])
{
    pid_t id = fork();

    if(id < 0){//子进程创建失败，设置错误码为99
        perror("fork");
        return ERROR;
    }
    else if(id == 0)//子进程
    {
        if(RDIR)
        {
            if(RDIR == OUT_RDIR){
                //输出重定向
                int fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                dup2(fd,1);
            }
            else if(RDIR == APPEND_RDIR){
                //追加重定向
                int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
                dup2(fd,1);
            }
            else{
                //输入重定向
                int fd = open(filename,O_RDWR,0666);
                dup2(fd,0);
            }
        }
        extern char** environ;//获取父亲环境变量
        execvpe(_argv[0],_argv,environ);
        exit(ERROR_CHILD);//如果没有进程替换，失败直接返回ERROR_CHILD
    }
    else//父进程
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);//阻塞等待
        if(rid == id)//子进程成功等待
        {
            if(WIFEXITED(status))//进程正常退出
                return WEXITSTATUS(status);
            else//进程正常退出，但是结果错误
                return ERROR_CHILD_RESULT;
        }
        else{//子进程异常
            exit(ERROR_CHILD);//子进程异常，重新输入命令行
        }
    }
}

int BulidCommand(char* _argv[])
{
    if(strcmp(_argv[0],"cd") == 0)
    {
        if(!_argv[1])return ERROR;
        //1.先获取当前的工作路径，使用系统调用接口getcwd实现
        //2.改变当前工作路径至argv指定的路径,使用sprintf实现(改变地址的函数)i
        //3.改变环境变量里pwd的值
        getpwd();
        chdir(_argv[1]);
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    return 0;
}

int main()
{

    int quit = 0;
    while(!quit) {//命令行的本质就是一个死循环
        RDIR = 0;
        filename = NULL;
        //2.交互问题，获取命令行
        Interact(commandline,sizeof(commandline));
        //3.字符串分割问题（传入argv数组里）
        int argc = Splitstring(commandline, argv);
        if(argc == 0) continue;
        //4.判断命令，由子进程或父进程完成
        int ret = BulidCommand(argv); 
        if(ret == ERROR) continue;
        //5.(子进程)执行命令
        if(!ret)CommandExceut(argv);
    }
    return 0;
}
